﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Management.Instrumentation;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;
using Microsoft.TeamFoundation.WorkItemTracking.Client;

namespace WIAssistant
{
    /// <summary>
    ///  This class contains one mapping between two WorkItemTypes.  The best and easiest mapping 
    /// is the same type in the same project.  The hardest types of mappings are different type in different projects.
    /// </summary>
    public class WorkItemTypeMapping: INotifyPropertyChanged
    {

        // The project we are copying from
        public Project SourceProject { get; set; }
        // The project we are copying to (could be the same as from)
        public Project DestProject { get; set; }

        private DispatcherTimer _errorMessageTimer;

        public WorkItemTypeMapping()
        {
            _errorMessageTimer = new DispatcherTimer();
            _errorMessageTimer.Tick += HideErrorMessage;
            _errorMessageTimer.Interval = new TimeSpan(0, 0, 2);
            _errorVisibility = Visibility.Collapsed;
        }
    

        
        /// <summary>
        /// Used to find the destination field that maps to the source field. 
        /// The key (first value) is the Destination field.  The Value (second value) is the source field
        /// This enables looking up the correct piece of data for a dest field via the dictionary.
        /// The idea is that we will create a new <see cref="WorkItem"/> (for a copy) and then iterate the fields of the 
        /// new <see cref="WorkItem"/> and use this collection to lookup the values from the source <see cref="WorkItem"/>.
        /// NOTE: this is just the mappings.  Do not use as a list of all fields for either dest or source!  Unmapped Items will not be in the list.
        /// </summary>
        public Dictionary<FieldDefinition, FieldDefinition> DestFieldMappings { get; set; }


        private readonly Dictionary<FieldDefinition, ContentControl> _contentControlDictionary = new Dictionary<FieldDefinition, ContentControl>();

        /// <summary>
        /// When we are moving the item off a match this is the target that is used
        /// </summary>
        public DestinationFieldDropTargetAdvisor DropFromMatch { get; set; }

        // Show or hide the error message
        private Visibility _errorVisibility;
        public Visibility ErrorVisibility
        {
            get { return _errorVisibility; }
            set
            {
                _errorVisibility = value;
                OnPropertyChanged("ErrorVisibility");
            }
        }

        /// <summary>
        /// Because the <see cref="DestFieldMappings"/> is not guaranteed to hold all the dest fields, we need to have them here for a list to select from.
        /// </summary>
        private List<DestinationField> _allDestFields;
        public List<DestinationField> AllDestFields
        {
            get
            {
                if (_allDestFields == null)
                    _allDestFields = GetDestFields();

                return _allDestFields;
            }
            set { _allDestFields = value; }
        }
        private List<DestinationField> GetDestFields()
        {
            List<DestinationField> destFields = new List<DestinationField>();
            foreach (FieldDefinition fieldDefinition in DestType.FieldDefinitions)
            {
                destFields.Add(new DestinationField(fieldDefinition));
            }
            return destFields;
        }

        
        /// <summary>
        /// Because the <see cref="DestFieldMappings"/> is not guaranteed to hold all the source fields, we need to have them here for a list to select from.
        /// </summary>
        private List<SourceField> _allSourceFields;
        public List<SourceField> AllSourceFields
        {
            get 
            {
                if (_allSourceFields == null)
                    _allSourceFields = GetSourceFields();
                
                return _allSourceFields;
            }
            set { _allSourceFields = value; }
        }
        private List<SourceField> GetSourceFields()
        {
            List<SourceField> sourceFields = new List<SourceField>();
            foreach (FieldDefinition fieldDefinition in SourceType.FieldDefinitions)
            {
                sourceFields.Add(new SourceField(fieldDefinition, DestType.FieldDefinitions, OnMappingMade, OnMappingFailed));
            }
            return sourceFields;
        }

        private void OnMappingFailed(FieldDefinition sourcefielddefinition, FieldDefinition destfield)
        {
            ErrorVisibility = Visibility.Visible;
            _errorMessageTimer.Start();
        }

        private void HideErrorMessage(object sender, EventArgs e)
        {
            _errorMessageTimer.Stop();
            ErrorVisibility = Visibility.Collapsed;
        }


        // this is event is called when a mapping is attempted.  Add the mapping to the list.
        private void OnMappingMade(FieldDefinition sourceFieldDefinition, FieldDefinition destFieldDefinition, Panel panel)
        {
            // If the source is null then we are actually removing the mapping
            if (sourceFieldDefinition == null)
            {
                RemoveMappedDestItem(destFieldDefinition);
            }
            else
            {
                AddDestItemToGUI(panel, destFieldDefinition);

                if (DestFieldMappings.ContainsKey(destFieldDefinition))
                    DestFieldMappings[destFieldDefinition] = sourceFieldDefinition;
                else
                    DestFieldMappings.Add(destFieldDefinition, sourceFieldDefinition);
                            
            }

            // Let the ui know the percent may have changed.
            OnPropertyChanged("PercentMapped");
            OnPropertyChanged("DestFieldMappings");
        }

        /// <summary>
        /// Remove the passed in item from the Matching.
        /// </summary>
        /// <param name="destFieldDef"></param>
        public void RemoveDestMappingFromGUI(FieldDefinition destFieldDef)
        {
            if (_contentControlDictionary.ContainsKey(destFieldDef))
            {
                // Remove it from the gui list
                ((Panel)_contentControlDictionary[destFieldDef].Parent)
                    .Children.Remove(_contentControlDictionary[destFieldDef]);

                // Remove it from the mapping list
                _contentControlDictionary.Remove(destFieldDef);

                // Change the mapping status

                DestinationField destField = AllDestFields.Find(dest => dest.DestField.Id == destFieldDef.Id);
                destField.IsMapped = false;

                // Let the ui know that the percent may have changed
                OnPropertyChanged("PercentMapped");

            }
        }

        private void AddDestItemToGUI(Panel panel, FieldDefinition destFieldDef)
        {
            // Remove any existing items so that we are only showing dest item per field
            // This is because we may have added something that is already mapped.
            // we need to have only one so we need to remove the existing one and map
            RemoveDestMappingFromGUI(destFieldDef);



            DestinationField destField = AllDestFields.Find(dest => dest.DestField.Id == destFieldDef.Id);
            destField.IsMapped = true;
            ContentControl control = new ContentControl {Content = destField };
            panel.Children.Add(control);
            // Save off the gui info so that we can change it if need be
            _contentControlDictionary.Add(destFieldDef, control);
        }

        /// <summary>
        /// Set the initial mapped values
        /// </summary>
        /// <param name="panel"></param>
        /// <param name="sourceFieldDef"></param>
        public void AddMappedDestItemGUI(Panel panel, FieldDefinition sourceFieldDef)
        {
            var destDefs = DestFieldMappings
                .Where(x =>
                {
                    if (x.Value != null)
                        return (x.Value.Id == sourceFieldDef.Id);
                    else
                        return false;
                });

            panel.Children.Clear();
            foreach (var destDef in destDefs)
            {
                AddDestItemToGUI(panel, destDef.Key);
            }
        }

        /// <summary>
        /// Remove a mapping
        /// </summary>
        /// <param name="destFieldDef"></param>
        public void RemoveMappedDestItem(FieldDefinition destFieldDef)
        {
            RemoveDestMappingFromGUI(destFieldDef);
            DestFieldMappings.Remove(destFieldDef);
        }


        // The source (copy from) work time type
        public WorkItemType SourceType { get; set; }

        // The dest (copy to) work time type
        private WorkItemType _destType;

        public WorkItemType DestType
        {
            get { return _destType; }
            set
            {
                _destType = value;

                // Setup a drop to unmapp this dest type's FieldDefinitions
                DropFromMatch = new DestinationFieldDropTargetAdvisor(null, DestType.FieldDefinitions, OnMappingMade, OnMappingFailed);

                OnDestTypeChanged();
            }
        }

        /// <summary>
        /// String of the percent of dest fields mapped
        /// </summary>
        public string PercentMapped
        {
            get
            {
                if (DestType == null)
                    return "0%";
                else
                {
                    int mappedItems = DestFieldMappings.Count(x => x.Value != null);
                    int totalItems = DestType.FieldDefinitions.Count;

                    int percent = (int) ((((double) mappedItems)/((double) totalItems))*100);

                    return percent + "%";
                }
            }
        }

        private void OnDestTypeChanged()
        {
            AutoMapFields();
        }

        
        /// <summary>
        /// Try to map between SourceType and DestType as best we can
        /// This is logic only (no GUI)
        /// </summary>
        public void AutoMapFields()
        {
            if ((SourceType == null) || (DestType == null))
                return;

            FieldDefinitionCollection sourceFields = SourceType.FieldDefinitions;
            FieldDefinitionCollection destFields = DestType.FieldDefinitions;

            var mappings = new Dictionary<FieldDefinition, FieldDefinition>();
            
            foreach (FieldDefinition destField in destFields)
            {
                bool matchMade = false;
                foreach (FieldDefinition sourceField in sourceFields)
                {
                    // See if the fields are equal.
                    if (destField.IsTheSameAs(sourceField))
                    {
                        matchMade = true;
                        mappings.Add(destField, sourceField);
                        break;
                    }
                }
                if (! matchMade)
                {
                    mappings.Add(destField, null);
                }
            }

            DestFieldMappings = mappings;

            // Let the ui know the percent may have changed.
            OnPropertyChanged("PercentMapped");
        }

        #region Property Changed Implementation
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        } 
        #endregion

        #region Equality Overloads
        public static bool operator == (WorkItemTypeMapping compareSource, WorkItemTypeMapping compareDest)
        {
            return (compareSource.SourceProject.Id == compareDest.SourceProject.Id) && (compareSource.SourceType.Name == compareDest.SourceType.Name);
        }

        public static bool operator !=(WorkItemTypeMapping compareSource, WorkItemTypeMapping compareDest)
        {
            return !(compareSource == compareDest);
        }

        public bool Equals(WorkItemTypeMapping other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return Equals(other.SourceProject.Id, SourceProject.Id) && Equals(other.SourceType.Name, SourceType.Name);
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != typeof (WorkItemTypeMapping)) return false;
            return Equals((WorkItemTypeMapping) obj);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                return ((SourceProject != null ? SourceProject.GetHashCode() : 0)*397) ^ (SourceType != null ? SourceType.GetHashCode() : 0);
            }
        }
        #endregion Equality Overloads
    }
    
}
